// -----------------------------------------------------------------------
// <copyright file="Stage.cs" company="The BomberJoe Team">
// Author: Emil D. Buhl <ebuh@itu.dk>
// </copyright>
// -----------------------------------------------------------------------

namespace BomberGame
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// The stage of the game, consisting of a grid of Bottoms.
    /// </summary>
    public class Stage
    {
        public readonly uint Height;

        public readonly uint Width;

        private readonly Queue<Tuple<Top, IntVector>> _addQueue;

        private readonly List<Player> _players;

        private readonly Dictionary<string, Texture2D> _textures;

        private readonly Bottom[,] _grid;

        /// <summary>
        /// Initializes a new instance of the Stage class.
        /// </summary>
        /// <param name="width">The width or the X-axis of the stage</param>
        /// <param name="height">The height or the y-axis of the stage</param>
        /// <param name="textures">a dictionary of the textures used to the draw the stage</param>
        /// <param name="player1">The player 1.</param>
        /// <param name="player2">The player 2.</param>
        public Stage(uint width, uint height, Dictionary<string, Texture2D> textures, Profile player1, Profile player2)
        {
            _textures = textures;
            Bomb.DefaultTexture = _textures["bomb"];
            Block.BombUpTexture = _textures["extra bomb"];
            Block.FireUpTexture = _textures["extra range"];
            Fire.DefaultTexture = _textures["fire"];
            Fire.CrossTexture = _textures["fire cross"];
            Fire.EndTexture = _textures["fire end"];
            Block.SpeedUpTexture = _textures["extra speed"];
            this.Width = width;
            this.Height = height;
            _grid = new Bottom[width, height];
            Populate();
            CurrentStage = this;
            _addQueue = new Queue<Tuple<Top, IntVector>>();
            _players = new List<Player>();
            IsGameover = false;

            _players.Add(
                new Player(
                    _textures[(player1.Score.Name.ToLower() == "joe" ? "joe" : "player1")],
                    player1,
                    new IntVector(1, 1)));
            _players.Add(
                new Player(
                    _textures[(player2.Score.Name.ToLower() == "joe" ? "joe" : "player2")],
                    player2,
                    new IntVector((int)Width - 2, (int)Height - 2)));
        }

        /// <summary>
        /// Gets the current Stage.
        /// </summary>
        [Pure]
        public static Stage CurrentStage { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the game is over.
        /// </summary>
        [Pure]
        public bool IsGameover { get; private set; }

        /// <summary>
        /// Update the game logic.
        /// </summary>
        /// <param name="gameTime">The game time</param>
        public void Update(GameTime gameTime)
        {
            foreach (Bottom bottom in _grid)
            {
                bottom.Update(gameTime);
            }

            while (_addQueue.Count > 0)
            {
                Tuple<Top, IntVector> temp = _addQueue.Dequeue();
                SetTop(temp.Item1, temp.Item2);
            }

            foreach (Player p in _players)
            {
                p.Update(gameTime);
            }

            uint count = 0;
            foreach (Player p in _players)
            {
                if (p.Active)
                {
                    count++;
                }
            }

            if (count < 2)
            {
                IsGameover = true;
            }
        }

        /// <summary>
        /// Draws the Stage
        /// </summary>
        /// <param name="spritebatch">The spritebatch with which to draw</param>
        public void Draw(SpriteBatch spritebatch)
        {
            foreach (Bottom bottom in _grid)
            {
                bottom.Draw(spritebatch);
            }

            foreach (Player p in _players)
            {
                p.Draw(spritebatch);
            }
        }

        /// <summary>
        /// Get Buttom as a certain vector
        /// </summary>
        /// <param name="vector">the coordinates of the bottom</param>
        /// <returns>The buttom at the defined coordinates</returns>
        [Pure]
        public Bottom GetBottom(IntVector vector)
        {
            return _grid[vector.X, vector.Y];
        }

        /// <summary>
        /// Add a top at certain coordinate
        /// </summary>
        /// <param name="top">The top to be added</param>
        /// <param name="vector">The coordinates the top is to be added</param>
        public void AddTop(Top top, IntVector vector)
        {
            Contract.Requires(GetBottom(vector).Top == null);
            Contract.Requires(!ContainsTop(GetBottom(vector)));

            _addQueue.Enqueue(new Tuple<Top, IntVector>(top, vector));
        }

        /// <summary>
        /// Remove a top at a certain coordinate.
        /// </summary>
        /// <param name="vector">The coordinates of where to remove a top</param>
        public void RemoveTop(IntVector vector)
        {
            Contract.Ensures(GetBottom(vector).Top == null);

            _grid[vector.X, vector.Y].Top = null;
        }

        /// <summary>
        /// Remove a top
        /// </summary>
        /// <param name="top">The top to be removed</param>
        public void RemoveTop(Top top)
        {
            Contract.Requires(GetBottom(top.GridVector).Top == top);
            Contract.Ensures(GetBottom(top.GridVector).Top == null);

            RemoveTop(top.GridVector);
        }

        /// <summary>
        /// Return a list of players present on the specified Bottom.
        /// </summary>
        /// <param name="bottom">The bottom to look for players</param>
        /// <returns>A list of players present on the bottom</returns>
        [Pure]
        public List<Player> GetPlayers(Bottom bottom)
        {
            Contract.Ensures(Contract.Result<List<Player>>().Count < 3);
            Contract.Ensures(Contract.Result<List<Player>>().Count > -1);
            return _players.Where(p => p.GridVector == bottom.GridVector).ToList();
        }

        /// <summary>
        /// Returns whether a Bottom contains a Top
        /// </summary>
        /// <param name="bottom">A Bottom</param>
        /// <returns>Whether the bottom contains a top</returns>
        [Pure]
        public bool ContainsTop(Bottom bottom)
        {
            return bottom.Top != null || _addQueue.Any(p => p.Item2 == bottom.GridVector);
        }

        public Top GetTop(Bottom bottom)
        {
            Contract.Requires(ContainsTop(bottom));
            return bottom.Top ?? _addQueue.Where(p => p.Item2 == bottom.GridVector).Single().Item1;
        }

        private void SetTop(Top top, IntVector gridVector)
        {
            _grid[gridVector.X, gridVector.Y].Top = top;
            top.GridVector = gridVector;
        }

        /// <summary>
        /// Populate the game grid with Bottoms and Tops. Must be done before the game begins.
        /// </summary>
        private void Populate()
        {
            Random random = new Random();
            for (int i = 0; i < Width; i++)
            {
                for (int j = 0; j < Height; j++)
                {
                    if (i == 0 || j == 0 || j == Height - 1 || i == Width - 1)
                    {
                        _grid[i, j] = new Bottom(_textures["wall"], false, new IntVector(i, j)); 
                    }
                    else if (i == 1 & j == 1 |
                             i == 1 & j == 2 |
                             i == 2 & j == 1 |
                             i == Width - 2 & j == Height - 2 |
                             i == Width - 2 & j == Height - 3 |
                             i == Width - 3 & j == Height - 2)
                    {
                        _grid[i, j] = new Bottom(_textures["floor"], true, new IntVector(i, j));
                    }
                    else if (i % 2 == 0 & j % 2 == 0)
                    {
                        _grid[i, j] = new Bottom(_textures["wall"], false, new IntVector(i, j));
                    } 
                    else
                    {
                        Bottom b = new Bottom(_textures["floor"], true, new IntVector(i, j));
                        _grid[i, j] = b;
                        if (random.NextDouble() < 0.8)
                        {
                            SetTop(new Block(_textures["block"]), new IntVector(i, j));
                        }
                    }
                }
            }
        }
    }
}
